package com.pobox.tupletest;

import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class Test extends ArrayList<Item> {
    /**
     * Logger for this class
     */
    private static final Logger logger = Logger.getLogger(Test.class);

    private static final long serialVersionUID = 1L;
    List<Boolean> dimensionChangePermitted = new ArrayList<Boolean>();
    transient Random randomizer = new Random(System.currentTimeMillis());

    public Test() {
        super();
    }

    public Test(Test test) {
        super(test);
    }

    public boolean covers(Tuple tuple) {
        logger.debug("covers(Tuple) - start");

        boolean returnboolean = containsAll(tuple);
        logger.debug("covers(Tuple) - end");
        return returnboolean;
    }

    public int countTuples(List<Tuple> wanted, List<Tuple> covered) {
        logger.debug("countTuples(List<Tuple>, List<Tuple>) - start");

        int coverageCount = 0;
        for (Tuple tuple : covered) {
            if (covers(tuple)) {
                coverageCount++;
            }
        }
        for (Tuple tuple : wanted) {
            if (covers(tuple)) {
                coverageCount++;
            }
        }

        logger.debug("countTuples(List<Tuple>, List<Tuple>) - end");
        return coverageCount;
    }

    public String report() {
        logger.debug("report() - start");

        StringBuilder sb = new StringBuilder();
        for (Item item : this) {
            sb.append(' ');
            sb.append(item.getDimension());
            sb.append(item.getFeature());
        }
        String returnString = sb.toString();
        logger.debug("report() - end");
        return returnString;
    }

    public void setDimensionChangePermission(int dimension, boolean permission) {
        logger.debug("setDimensionChangePermission(int, boolean) - start");

        growPermissionArrayIfRequired();
        dimensionChangePermitted.set(dimension, Boolean.valueOf(permission));

        logger.debug("setDimensionChangePermission(int, boolean) - end");
    }

    void growPermissionArrayIfRequired() {
        logger.debug("growPermissionArrayIfRequired() - start");

        // implicit permission granted
        if (dimensionChangePermitted.size() < size()) {
            for (int i = dimensionChangePermitted.size(); i < size(); i++) {
                dimensionChangePermitted.add(Boolean.TRUE);
            }
        }

        logger.debug("growPermissionArrayIfRequired() - end");
    }

    public boolean isDimensionChangePermitted(int dimension) {
        logger.debug("isDimensionChangePermitted(int) - start");

        growPermissionArrayIfRequired();
        Boolean permission = dimensionChangePermitted.get(dimension);
        boolean returnboolean = permission.booleanValue();
        logger.debug("isDimensionChangePermitted(int) - end");
        return returnboolean;
    }

    public List<Integer> getListOfMutableDimensions() {
        logger.debug("getListOfMutableDimensions() - start");

        List<Integer> list = new ArrayList<Integer>(size());
        for (int i = 0; i < size(); i++) {
            boolean mutable = isDimensionChangePermitted(i);
            if (mutable) {
                list.add(Integer.valueOf(i));
            }
        }

        logger.debug("getListOfMutableDimensions() - end");
        return list;
    }

    Test pluginTupleToTestCase(Tuple tuple) {
        logger.debug("pluginTupleToTestCase(Tuple) - start");

        for (Item item : tuple) {
            int dimensionToReplace = item.getDimension() - 1;
            set(dimensionToReplace, item);
        }

        logger.debug("pluginTupleToTestCase(Tuple) - end");
        return this;
    }

    public int maximizeCoverage(Table table) {
        logger.debug("maximizeCoverage(Table) - start");

        assert table != null : "table cannot be null";
        boolean progress;
        List<Tuple> wantedTuples = table.getAllWantedTuples(table
                .getItemComboPerTuple());
        List<Tuple> coveredTuples = table.getCoveredTuples();
        List<Integer> mutableDimensions = getListOfMutableDimensions();

        // repeatedly loop through all dimensions, maximizing tuple coverage
        do {
            logger.debug("starting progress loop");
            progress = false; // assume no improvement in tuple coverage
            logger.debug("mutable dimensions" + mutableDimensions);
            Collections.shuffle(mutableDimensions); // shuffle the dimensions
            logger.debug("mutable dimensions - after shuffle"
                    + mutableDimensions);

            for (Integer integer : mutableDimensions) {
                logger.debug("looping over dimensions- doing: " + integer);
                int workingDimension = integer.intValue();
                Item bestItem = get(workingDimension);
                logger.debug("best item is set to: " + bestItem);
                Tuple best = new Tuple();
                int coverage = countTuples(wantedTuples, coveredTuples);
                logger.debug("coverage for me is: " + coverage);
                for (Item item : table.get(workingDimension)) {
                    logger.debug("looping over features- doing: " + item);
                    Test improvedTest = new Test(this);
                    improvedTest.set(workingDimension, item);
                    logger.debug("improved test is set: " + improvedTest);
                    int newCoverage = improvedTest.countTuples(wantedTuples,
                            coveredTuples);
                    logger.debug("improved test new coverage: " + newCoverage);

                    if (item.compareTo(bestItem) < 0) {
                        logger
                                .debug("this item < previous bestItem, swapping them");
                        bestItem = item;
                        progress = true;
                        coverage = newCoverage;
                        best.clear();
                        best.add(item);
                    } else if (item.equals(bestItem) && newCoverage >= coverage) {
                        logger
                                .debug("same item with same or better coverage- maybe adding or swapping");
                        if (newCoverage > coverage) {
                            logger
                                    .debug("... ok - better coverage, so swapping them");
                            progress = true;
                            coverage = newCoverage;
                            best.clear();
                        }
                        best.add(item);
                    }
                }
                swapToBestItemSeen(workingDimension, best);
            }
        } while (progress);
        logger.debug("exited progress loop");
        // TODO remove covered tuples
        int returnint = countTuples(wantedTuples, table
                .getCoveredTuples());
        logger.debug("maximizeCoverage(Table) - end");
        return returnint;
    }

    void swapToBestItemSeen(int workingDimension, Tuple best) {
        logger.debug("swapToBestItemSeen(int, Tuple) - start - "
                + workingDimension + " : " + best);

        // Change this dimension to the best features seen.
        // Worst case, everyone was worse than the old value, so best.get(0)
        // will be the old value. Coverage will be the same and still no
        // withouts will be hit. If there are many 'best' features for this
        // dimension, then randomly choose one of them to insert
        assert null != best : "best Tuple cannot be null";
        assert !best.isEmpty() : "best Tuple cannot be empty";

        Collections.shuffle(best, randomizer);
        logger.debug("shuffled best tuple to: " + best);
        logger.debug("swapping out for this test: " + get(workingDimension)
                + " for: " + best.get(0));
        set(workingDimension, best.get(0));

        logger.debug("swapToBestItemSeen(int, Tuple) - end");
    }
}